﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Xml.Serialization;


namespace advadev.DataStorage
{

    /// <summary>
    /// Represents a generic entity memory data storage
    /// </summary>
    public class GenericEntityMemoryDataStorage : IGenericEntityDataStorage
    {

        /// <summary>for internal use</summary>
        private Dictionary<string, Dictionary<string, Dictionary<string, GenericEntity>>> _dataStorage = new Dictionary<string, Dictionary<string, Dictionary<string, GenericEntity>>>();


        /// <summary>
        /// Initializes a new instance of the GenericEntityMemoryDataStorage
        /// class
        /// </summary>
        public GenericEntityMemoryDataStorage()
        {
        }


        #region IGenericEntityDataStorage implementation
        /// <summary>
        /// Saves the given generic entity
        /// </summary>
        /// <param name="genericEntity">
        /// Generic entity to save
        /// </param>
        public void Save(GenericEntity genericEntity)
        {
            if (genericEntity == null)
            {
                return;
            }
            GenericEntity existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
            if (existingItem == null)
            {
                // -> insert
                if (!_dataStorage.ContainsKey(genericEntity.ApplicationKey))
                {
                    _dataStorage.Add(genericEntity.ApplicationKey, new Dictionary<string, Dictionary<string, GenericEntity>>());
                }
                if (!_dataStorage[genericEntity.ApplicationKey].ContainsKey(genericEntity.FullClassName))
                {
                    _dataStorage[genericEntity.ApplicationKey].Add(genericEntity.FullClassName, new Dictionary<string, GenericEntity>());
                }
                _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName].Add(genericEntity.EntityId, clone(genericEntity));
            }
            else
            {
                // -> update
                _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName][genericEntity.EntityId] = clone(genericEntity);
            }
        }

        /// <summary>
        /// Saves the given list of generic entities
        /// </summary>
        /// <param name="genericEntities">
        /// List of generic entities to save
        /// </param>
        public void Save(IList<GenericEntity> genericEntities)
        {
            if (genericEntities == null || genericEntities.Count == 0)
            {
                return;
            }
            foreach (GenericEntity genericEntity in genericEntities)
            {
                GenericEntity existingItem = findExisting(genericEntity.EntityId, genericEntity.ApplicationKey, genericEntity.FullClassName);
                if (existingItem == null)
                {
                    // -> insert
                    if (!_dataStorage.ContainsKey(genericEntity.ApplicationKey))
                    {
                        _dataStorage.Add(genericEntity.ApplicationKey, new Dictionary<string, Dictionary<string, GenericEntity>>());
                    }
                    if (!_dataStorage[genericEntity.ApplicationKey].ContainsKey(genericEntity.FullClassName))
                    {
                        _dataStorage[genericEntity.ApplicationKey].Add(genericEntity.FullClassName, new Dictionary<string, GenericEntity>());
                    }
                    _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName].Add(genericEntity.EntityId, clone(genericEntity));
                }
                else
                {
                    // -> update
                    _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName][genericEntity.EntityId] = clone(genericEntity);
                }
            }
        }

        /// <summary>
        /// Finds the generic entity with the given id, application key and type
        /// </summary>
        /// <param name="id">
        /// Id of the generic entity
        /// </param>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        /// <returns>
        /// The generic entity or null
        /// </returns>
        public GenericEntity Find(string id, string applicationKey, string fullClassName)
        {
            if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return null;
            }
            if (_dataStorage.ContainsKey(applicationKey) && _dataStorage[applicationKey].ContainsKey(fullClassName) && _dataStorage[applicationKey][fullClassName].ContainsKey(id))
            {
                return _dataStorage[applicationKey][fullClassName][id];
            }
            return null;
        }

        /// <summary>
        /// Selects all generic entities with the given application key and type
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        /// <returns>
        /// A list of generic entities
        /// </returns>
        public IList<GenericEntity> Select(string applicationKey, string fullClassName)
        {
            IList<GenericEntity> result = new List<GenericEntity>();
            if (string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return result;
            }
            if (_dataStorage.ContainsKey(applicationKey) && _dataStorage[applicationKey].ContainsKey(fullClassName))
            {
                foreach (GenericEntity genericEntity in _dataStorage[applicationKey][fullClassName].Values)
                {
                    result.Add(genericEntity);
                }
            }
            return result;
        }

        /// <summary>
        /// Deletes the given generic entity
        /// </summary>
        /// <param name="genericEntity">
        /// Generic entity to delete
        /// </param>
        public void Delete(GenericEntity genericEntity)
        {
            if (genericEntity == null)
            {
                return;
            }
            if (_dataStorage.ContainsKey(genericEntity.ApplicationKey) && _dataStorage[genericEntity.ApplicationKey].ContainsKey(genericEntity.FullClassName) && _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName].ContainsKey(genericEntity.EntityId))
            {
                _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName].Remove(genericEntity.EntityId);
            }
        }

        /// <summary>
        /// Deletes the given list of generic entities
        /// </summary>
        /// <param name="genericEntities">
        /// List of generic entities to delete
        /// </param>
        public void Delete(IList<GenericEntity> genericEntities)
        {
            if (genericEntities == null || genericEntities.Count == 0)
            {
                return;
            }
            foreach (GenericEntity genericEntity in genericEntities)
            {
                if (_dataStorage.ContainsKey(genericEntity.ApplicationKey) && _dataStorage[genericEntity.ApplicationKey].ContainsKey(genericEntity.FullClassName) && _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName].ContainsKey(genericEntity.EntityId))
                {
                    _dataStorage[genericEntity.ApplicationKey][genericEntity.FullClassName].Remove(genericEntity.EntityId);
                }
            }
        }

        /// <summary>
        /// Deletes all generic entities with the given application key and type
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name of the type
        /// </param>
        public void Delete(string applicationKey, string fullClassName)
        {
            if (string.IsNullOrEmpty(applicationKey) || string.IsNullOrEmpty(fullClassName))
            {
                return;
            }
            if (_dataStorage.ContainsKey(applicationKey) && _dataStorage[applicationKey].ContainsKey(fullClassName))
            {
                _dataStorage[applicationKey][fullClassName].Clear();
            }
        }

        /// <summary>
        /// Deletes all generic entities with the given application key
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        public void Delete(string applicationKey)
        {
            if (string.IsNullOrEmpty(applicationKey))
            {
                return;
            }
            if (_dataStorage.ContainsKey(applicationKey))
            {
                _dataStorage[applicationKey].Clear();
            }
        }

        /// <summary>
        /// Finds all full class names with the given application key
        /// </summary>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <returns>
        /// List of full class names or empty list
        /// </returns>
        public IList<string> FindFullClassNames(string applicationKey)
        {
            IList<string> result = new List<string>();
            if (string.IsNullOrEmpty(applicationKey))
            {
                return result;
            }
            if (_dataStorage.ContainsKey(applicationKey))
            {
                foreach (string fullClassName in _dataStorage[applicationKey].Keys)
                {
                    if (!result.Contains(fullClassName))
                    {
                        result.Add(fullClassName);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Finds all application keys
        /// </summary>
        /// <returns>
        /// List of application keys or empty list
        /// </returns>
        public IList<string> FindApplicationKeys()
        {
            IList<string> result = new List<string>();
            foreach (string applicationKey in _dataStorage.Keys)
            {
                if (!result.Contains(applicationKey))
                {
                    result.Add(applicationKey);
                }
            }
            return result;
        }
        #endregion IGenericEntityDataStorage implementation


        #region helpers
        /// <summary>
        /// Finds a existing generic database entity
        /// </summary>
        /// <param name="entityId">
        /// Entity id
        /// </param>
        /// <param name="applicationKey">
        /// Application key
        /// </param>
        /// <param name="fullClassName">
        /// Full class name
        /// </param>
        /// <returns>
        /// Generic database entity or null
        /// </returns>
        private GenericEntity findExisting(string entityId, string applicationKey, string fullClassName)
        {
            if (_dataStorage.ContainsKey(applicationKey))
            {
                if (_dataStorage[applicationKey].ContainsKey(fullClassName))
                {
                    if (_dataStorage[applicationKey][fullClassName].ContainsKey(entityId))
                    {
                        return _dataStorage[applicationKey][fullClassName][entityId];
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Clones the given item
        /// </summary>
        /// <typeparam name="T">
        /// Generic object type
        /// </typeparam>
        /// <param name="item">
        /// Item to clone
        /// </param>
        /// <returns>
        /// Cloned item or default item
        /// </returns>
        static private GenericEntity clone(GenericEntity item)
        {
            StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
            XmlWriter xmlWriter = XmlWriter.Create(writer);
            XmlSerializer serializer = new XmlSerializer(typeof(GenericEntity));
            serializer.Serialize(xmlWriter, item);
            string xml = writer.ToString();
            StringReader reader = new StringReader(xml);
            XmlReader xmlReader = XmlReader.Create(reader);
            return serializer.Deserialize(xmlReader) as GenericEntity;
        }
        #endregion helpers

    }

}
